package flockingGA;

import jnibwapi.BWAPIEventListener;
import jnibwapi.JNIBWAPI;
import jnibwapi.model.Unit;
import jnibwapi.types.UnitType;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.HashSet;

import jenes.GeneticAlgorithm;
import jenes.chromosome.DoubleChromosome;
import jenes.population.Fitness;
import jenes.population.Individual;
import jenes.population.Population;
import jenes.stage.operator.common.OnePointCrossover;
import jenes.stage.operator.common.SimpleMutator;
import jenes.stage.operator.common.TournamentSelector;

/**
 * Created by Stefan Rudolph on 17.02.14.
 */
public class FlockingMarineAI implements BWAPIEventListener, Runnable {

    private final JNIBWAPI bwapi;

    private HashSet<Marine> marines;

    private HashSet<Unit> enemyUnits;

    private int frame;
    private int marineID = 0;
    
    
    private static final int POPULATION_SIZE = 100;
    private static final int CHROMOSOME_LENGTH = 11; // got 11 values
    private static final int GENERATION_LIMIT = 1000;
    public static final int TOURNAMENT_SIZE = 4;
    private static final double CROSSOVER_PROBABILITY = 0.75;
    private static final double MUTATION_PROBABILITY = 0.025;
    
    private final GAState gaState;
    private Parameters params;
    
    public FlockingMarineAI(GAState gaState) {
        System.out.println("This is the FlockingMarineAI GA! :)");
        this.gaState = gaState;
        bwapi = new JNIBWAPI(this, false);
    }

    public static void main(String[] args) {
    	GAState gaState = new GAState();
    	
    	Fitness<DoubleChromosome> fit = new FitnessDoubleChromosomeSync(gaState);
    	Individual<DoubleChromosome> sample = new Individual<DoubleChromosome>(new DoubleChromosome(CHROMOSOME_LENGTH, 0, 1));
    	Population<DoubleChromosome> pop = new Population<>(sample, POPULATION_SIZE);
    	GeneticAlgorithm<DoubleChromosome> ga = new GeneticAlgorithm<>(fit, pop, GENERATION_LIMIT);
    	ga.setFullEvaluationForced(true);
    	
    	ga.addStage(new TournamentSelector<DoubleChromosome>(TOURNAMENT_SIZE));
		ga.addStage(new OnePointCrossover<DoubleChromosome>(CROSSOVER_PROBABILITY));
		ga.addStage(new SimpleMutator<DoubleChromosome>(MUTATION_PROBABILITY));
    	
    	new Thread(new FlockingMarineAI(gaState)).start();        
        ga.evolve();
    }

    @Override
    public void matchStart() {
    	while (gaState.getState()!=1)
			try {
				Thread.sleep(125);
			} catch (InterruptedException e) {
			}
		params = gaState.getParams();
		
        marines = new HashSet<>();
        enemyUnits = new HashSet<>();

        frame = 0;

        bwapi.enablePerfectInformation();
        bwapi.enableUserInput();
        bwapi.setGameSpeed(0);
    }

    @Override
    public void matchFrame() {
        for (Marine m : marines) {
            m.step();
        }

        if (frame % 1000 == 0) {
            System.out.println("Frame: " + frame);
        }
        frame++;
    }

    @Override
    public void unitDiscover(int unitID) {
        Unit unit = bwapi.getUnit(unitID);
        int typeID = unit.getTypeID();

        if (typeID == UnitType.UnitTypes.Terran_Marine.getID()) {
            if (unit.getPlayerID() == bwapi.getSelf().getID()) {
                marines.add(new Marine(unit, bwapi, enemyUnits, marineID,this.params));
                marineID++;
            } else {
                enemyUnits.add(unit);
            }
        } else if (typeID == UnitType.UnitTypes.Terran_Vulture.getID()) {
            if (unit.getPlayerID() != bwapi.getSelf().getID()) {
                enemyUnits.add(unit);
            }
        }
    }

    @Override
    public void unitDestroy(int unitID) {
        Marine rm = null;
        for (Marine marine : marines) {
            if (marine.getID() == unitID) {
                rm = marine;
                break;
            }
        }
        if( rm != null )
        {
	        marines.remove(rm);
	        System.out.println("Marines left: "+marines.size());
        }

        Unit rmUnit = null;
        for (Unit u : enemyUnits) {
            if (u.getID() == unitID) {
                rmUnit = u;
                break;
            }
        }
        if( rmUnit != null )
        {
        	enemyUnits.remove(rmUnit);
	        System.out.println("Enemies left: "+enemyUnits.size());
        }
    }

    @Override
    public void connected() {
        System.out.println("Connected2");
    }

    @Override
    public void matchEnd(boolean winner) {
    	// save results
    	int ownHealth = 0;
    	int enemyHealth = 0;
    	try
    	{
    	    //create a temporary file
    	    File logFile=new File("results.txt");
    	    BufferedWriter writer = new BufferedWriter(new FileWriter(logFile,true));
    	    
    	    String s = new String();

	    	
	    	for( Unit u: bwapi.getMyUnits())
	    	{
	    		ownHealth += u.getHitPoints();
	    	}
	    	s += ""+ownHealth+ " ";

    	    for(Unit u : bwapi.getEnemyUnits())
    	    {
    	    	enemyHealth += u.getHitPoints();
    	    }
    	    s += ""+enemyHealth+" "+winner+"\n";
    	    
    	    writer.write (s);
    	    writer.flush();
    	    writer.close();
    	} catch(Exception e)
    	{
    	    e.printStackTrace();
    	}
    	System.out.println("Won: "+winner);
    	
    	while (gaState.getState() != 2) {
			try {
				Thread.sleep(125);
			} catch (InterruptedException e) {
			}
		}
    	gaState.setScore(ownHealth-enemyHealth);
    }

    @Override
    public void keyPressed(int keyCode) {

    }

    @Override
    public void sendText(String text) {

    }

    @Override
    public void receiveText(String text) {

    }

    @Override
    public void playerLeft(int playerID) {

    }

    @Override
    public void nukeDetect(int x, int y) {

    }

    @Override
    public void nukeDetect() {

    }

    @Override
    public void unitEvade(int unitID) {

    }

    @Override
    public void unitShow(int unitID) {

    }

    @Override
    public void unitHide(int unitID) {

    }

    @Override
    public void unitCreate(int unitID) {
    }

    @Override
    public void unitMorph(int unitID) {

    }

    @Override
    public void unitRenegade(int unitID) {

    }

    @Override
    public void saveGame(String gameName) {

    }

    @Override
    public void unitComplete(int unitID) {

    }

    @Override
    public void playerDropped(int playerID) {

    }

    @Override
    public void run() {
        bwapi.start();
    }
}


